//==================================================================
//  Copyright (C) 2006-2007  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
///
///
///
///
//==================================================================

#include "pnetlib_drtysock.h"
#include "pnetlib_socksupport.h"
#include "pnetlib_comiofile.h"

#define INBUFF_SIZE	(32*1024)

#define PRINT_SOCK_ERR
static u_int	_send_ticket_cnt=1;	// 0 is reserved for no ticket
static u_char	_in_buff[INBUFF_SIZE];

//==================================================================
typedef enum
{
	NOT_CONNECTED=0,
	CONNECTING=1,
	CONNECTED=2
} status_e;

//==================================================================
static int setup_nodelay( int fd )
{
	unsigned long int On = 0;

	if ( setsockopt( fd, IPPROTO_TCP, TCP_NODELAY, (const char *)&On, sizeof(On) ) < 0 )
	{
		return -1;
	}

	return 0;
}

//=============================================================================
///
//=============================================================================
//namespace PASCALIBS {

//==================================================================
ComIOFile::ComIOFile() :
	_status(0),
	_fd(-1),
	_einval_cnt(0),
	_out_sap(NULL),
	_outdatap(NULL),
	_outdata_size(0),
	_outdata_done(false),
	_outdata_ticket(0),
	_indata_is_ready(false)
{
}

//==================================================================
int ComIOFile::Connect( const char *ipnamep, int port_number )
{
	struct hostent		*hp;
	u_long				addr;
	u_long				blockflg;

	addr = inet_addr( ipnamep );
	if ( addr == INADDR_NONE )
	{
		if NOT( hp = gethostbyname( ipnamep ) )
		{
			return COM_ERR_INVALID_ADDRESS;
		}
		addr = *(long *)hp->h_addr;
	}

	_out_sap = new sockaddr_in;

	memset( _out_sap, 0, sizeof(*_out_sap) );
	memcpy( &_out_sap->sin_addr, &addr, sizeof(long) );
	_out_sap->sin_family = AF_INET;
	_out_sap->sin_port = htons( port_number );

	if ( (_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0 )
	{
		PRINT_SOCK_ERR;
		return -1;
	}
	/*
	if ( bind( _io_fd, (struct sockaddr *)&_out_sa, sizeof(_out_sa) ) < 0 )
	{
		PRINT_SOCK_ERR;
		closesocket( _io_fd );
		_io_fd = -1;
		return RDCON_ERROR;
	}
	*/

	if ( setup_nodelay( _fd ) )
		return -1;

	blockflg = 1;
	if ( ioctlsocket( _fd, FIONBIO, &blockflg ) < 0 )
	{
		PRINT_SOCK_ERR;
		closesocket( _fd );
		_fd = -1;
		return -1;
	}
	_status = CONNECTING;

	return COM_ERR_NONE;}

//==================================================================
int ComIOFile::on_disconnect( int printerror )
{
	int err = LAST_SOCK_ERR;
	PASSERT( _status != NOT_CONNECTED );

	_status = NOT_CONNECTED;
	closesocket( _fd );
	_fd = -1;

	if ( err == ECONNRESET )	return COM_ERR_HARD_DISCONNECT; else
	if ( err == EINVAL )		return COM_ERR_INVALID_ADDRESS;
	//ECONNABORTED

	return COM_ERR_GRACEFUL_DISCONNECT;
}

//==================================================================
int ComIOFile::handle_connecting()
{
	if ( _fd < 0 )
		return -1;

	if ( connect( _fd, (struct sockaddr *)_out_sap, sizeof(*_out_sap)) < 0 )
	{
	int	err = LAST_SOCK_ERR;

		if ( err )
		{
			if ( err == EISCONN )
			{
				_status = CONNECTED;
				return COM_ERR_CONNECTED;
			}
			else
			if ( err == EWOULDBLOCK || err == EALREADY )
				return COM_ERR_NONE;
			else
			if ( err == EINVAL )
			{
				if ( ++_einval_cnt < 2000 )
				{
					Sleep(5);
					return COM_ERR_NONE;
				}
				else
					return on_disconnect( 0 );
			}
			else
				return on_disconnect( 1 );
		}
	}

	return COM_ERR_NONE;
}

//==================================================================
int ComIOFile::handle_send()
{
int	sent_len;

	if NOT( _outdatap )
		return COM_ERR_NONE;

	sent_len = send( _fd, (const char *)(_outdatap + _outdata_done),
					 _outdata_size - _outdata_done, 0 );
	if ( sent_len <= 0 )
	{
		switch ( LAST_SOCK_ERR )
		{
		case EWOULDBLOCK:
			if ( sent_len == 0 )
				return COM_ERR_GRACEFUL_DISCONNECT;

			return COM_ERR_NONE;

		case ECONNRESET:
			return COM_ERR_HARD_DISCONNECT;

		default:
			return COM_ERR_HARD_DISCONNECT;
		}
	}
	else
	{
		_outdata_done += sent_len;
		PASSERT( _outdata_done <= _outdata_size );

		if ( _outdata_done >= _outdata_size )
		{
			if ( _outdatap )
			{
				delete [] _outdatap;
				_outdatap = NULL;
			}

			_outdata_size = 0;
			_outdata_done = 0;
		}
	}
	
	return COM_ERR_NONE;
}

//==================================================================
int ComIOFile::handle_recv()
{
int	recvd_len;

	recvd_len = recv( _fd, (char *)_in_buff, sizeof(_in_buff), 0 );

	if ( recvd_len <= 0 )
	{
		if ( recvd_len == 0 )
		{
			_indata_is_ready = true;
			return COM_ERR_GRACEFUL_DISCONNECT;
		}

		switch ( LAST_SOCK_ERR )
		{
		case EWOULDBLOCK:
			return COM_ERR_NONE;

		case ECONNRESET:
			return COM_ERR_HARD_DISCONNECT;
		}

		return COM_ERR_HARD_DISCONNECT;
	}
	else
	{
		_indata.WriteData( _in_buff, recvd_len );

		return COM_ERR_NONE;
	}
}

//==================================================================
int ComIOFile::Idle()
{
	struct timeval	tv;
	fd_set			rdset, wtset;
	int				err;

	FD_ZERO( &rdset );
	FD_ZERO( &wtset );

	switch ( _status )
	{
	case CONNECTING:
		return handle_connecting();

	case CONNECTED:
							FD_SET( _fd, &rdset );
		if ( _outdatap )	FD_SET( _fd, &wtset );
		tv.tv_sec = 0;
		tv.tv_usec = 0;

		if ( select( 2, &rdset, &wtset, 0, &tv ) == -1 )
			return on_disconnect( 1 );

		if ( FD_ISSET( _fd, &wtset ) )
		{
			if ( err = handle_send() )
				return err;
		}
		if ( FD_ISSET( _fd, &rdset ) )
		{
			if ( err = handle_recv() )
				return err;
		}

		break;
	}

	return COM_ERR_NONE;
}

//==================================================================
ComIOFile::~ComIOFile()
{
	SAFE_DELETE( _out_sap );
	delete [] _outdatap;
}

//==================================================================
int ComIOFile::Send( void *datap, int size, u_int *send_ticketp )
{
	if ( _outdatap )
		return COM_ERR_GENERIC;

	if NOT( _outdatap = new u_char[ size ] )
		return COM_ERR_GENERIC;

	memcpy( _outdatap, datap, size );
	_outdata_size = size;
	_outdata_done = 0;
	_outdata_ticket = _send_ticket_cnt;
	if ( send_ticketp )
		*send_ticketp = _send_ticket_cnt;

	++_send_ticket_cnt;	
	if ( _send_ticket_cnt == 0 )	// 0 is reserved for no ticket
		_send_ticket_cnt = 1;

	return COM_ERR_NONE;
}

//==================================================================
bool ComIOFile::IsDataSent( u_int send_ticket )
{
	if ( _outdata_ticket == send_ticket )
		return false;

	return true;
}

//=============================================================================
//}